Skip to content

feat: forward client request headers to upstream providers in bridge routes#195

Closed
ssncferreira wants to merge 2 commits intomainfrom
ssncf/feat-forward-client-headers
Closed

feat: forward client request headers to upstream providers in bridge routes#195
ssncferreira wants to merge 2 commits intomainfrom
ssncf/feat-forward-client-headers

Conversation

@ssncferreira
Copy link
Contributor

@ssncferreira ssncferreira commented Feb 27, 2026

Description

AI Bridge drops client request headers when making upstream API calls through bridge routes. The interceptors construct new HTTP requests via provider SDKs, which set their own headers and discard the originals.

Rather than managing specific headers per provider (as done with Copilot's ExtraHeaders), this PR forwards all client headers to upstream providers, stripping only those that are unsafe or managed by the SDK.

Changes

  • Add intercept/client_headers.go with SanitizeClientHeaders that clones client headers and strips hop-by-hop and transport-managed headers before forwarding.
  • Forward sanitized client headers to upstream providers in all bridge interceptors (Anthropic messages, OpenAI chat completions, OpenAI responses).
  • Client headers are applied before SDK options so that auth and other SDK-managed headers take priority on conflict.
  • Update all three providers (Anthropic, OpenAI, Copilot) to pass client headers to interceptor constructors.

Follow-up

The following changes will be addressed in follow-up PRs to reduce scope:

  • Proxy headers: Passthrough routes set standard proxy headers (X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto), while bridge routes do not. Since AI Bridge behaves as a proxy, this handling should be consistent across all requests.
  • Copilot extra headers removal: This PR makes the ExtraHeaders mechanism introduced for Copilot redundant, as all client headers are now forwarded. It can be safely removed.
  • Anthropic extra headers removal: This PR makes the ExtraHeaders mechanism introduced for Anthropic in #205 redundant, as all client headers are now forwarded. It can be safely removed.

Closes: #192

Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@ssncferreira ssncferreira force-pushed the ssncf/feat-forward-client-headers branch 5 times, most recently from 4544e46 to 8c0c002 Compare March 6, 2026 09:48
@ssncferreira ssncferreira marked this pull request as ready for review March 6, 2026 09:56
@ssncferreira ssncferreira force-pushed the ssncf/feat-forward-client-headers branch 2 times, most recently from 78cdc43 to 2a43c3e Compare March 6, 2026 16:29
return nil, fmt.Errorf("unmarshal request body: %w", err)
}

cfg := p.cfg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove cfg := p.cfg and use p.cfg line below

// below takes priority on any conflict.
for k, vals := range intercept.SanitizeClientHeaders(i.clientHeaders) {
for _, v := range vals {
opts = append(opts, option.WithHeader(k, v))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithHeader uses Header.Set which overrides header value instead of adding it.
I think WithHeaderAdd would help here. Test case needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some headers, it makes sense to use Add, but for others not so much. For instance, the SDK sets X-Stainless-* headers, and the client sends its own. Using Add, the upstream would receive both values. Using Set, the upstream sees only the client's value, which also does not seem correct since AI Bridge is the one making the request.
This is a good point, and makes me wonder if this is the right direction 🤔 I added this as a topic to discuss at the weekly


func (i *interceptionBase) newCompletionsService() openai.ChatCompletionService {
opts := []option.RequestOption{option.WithAPIKey(i.cfg.Key), option.WithBaseURL(i.cfg.BaseURL)}
var opts []option.RequestOption
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option order matters? I'd assume it should not so old way of initializing opts could stay?

// below takes priority on any conflict.
for k, vals := range intercept.SanitizeClientHeaders(i.clientHeaders) {
for _, v := range vals {
opts = append(opts, option.WithHeader(k, v))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WithHeader -> WithHeaderAdd

@ssncferreira ssncferreira force-pushed the ssncf/feat-forward-client-headers branch from 9b666ae to 2b13d58 Compare March 10, 2026 10:45
@ssncferreira ssncferreira marked this pull request as draft March 11, 2026 08:40
@dannykopping
Copy link
Collaborator

@ssncferreira shall we close this, following our discussion?

@ssncferreira
Copy link
Contributor Author

Closing in favor of #214

@ssncferreira ssncferreira deleted the ssncf/feat-forward-client-headers branch March 12, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forward client request headers to upstream providers

3 participants